#include "client.h"
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <string.h>
#include <pthread.h>
#include <assert.h>
#include <malloc.h>
#include "../shared.h"
#include "net.h"
#include "memtool.h"

#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))


struct client_shared_ctx *create_shared_ctx(int argc, char *argv[]){
  BUILD_BUG_ON(sizeof(int) != 4);
  BUILD_BUG_ON(sizeof(long) != 8);
  struct client_shared_ctx *sctx;
  char c;
  int ib_port = 1, mc_port = 0;
  int cache_g_size = 0;
  int res;
  char mc_ip[SHORT_STR_LEN] = {0};
  static struct option opts[] = {
    {"ib-port", 1, NULL, 'i'},
    {"memcached-server-ip", 1, NULL, 'X'},
    {"memcached-server-port", 1, NULL, 'P'},
    {"cache-g-size", 1, NULL, 'c'},
    {NULL, 0, NULL, 0}
  };
  srand(time(NULL));
  /* Parse and check arguments */
  while (1) {
    c = getopt_long(argc, argv, "m:s:c:X:P", opts, NULL);
    if (c == -1) {
      break;
    }
    switch (c) {
    case 'i':
      ib_port = atoi(optarg);
      p_info("ib port :%d\n", ib_port);
      break;
    case 'X':
      if(strlen(optarg) >= SHORT_STR_LEN){
        p_die("ip string too long\n");
      }
      strcpy(mc_ip, optarg);
      p_info("memcached-server-ip = %s\n", mc_ip);
      break;
    case 'P':
      mc_port = atoi(optarg);
      p_info("memcached-server-port = %d\n",mc_port);
      break;
    case 'c':
      cache_g_size = atoi(optarg);
      p_info("%dG cache\n", cache_g_size);
      break;
    default:
      p_die("Invalid argument %c\n", c);
    }
  }
  if(strlen(mc_ip) == 0 || !mc_port){
    p_die("arguement is wrong\n");
  }
  sctx = (struct client_shared_ctx *)malloc(sizeof(client_shared_ctx));
  assert(sctx);
  memset(sctx, 0, sizeof(struct client_shared_ctx));
  strcpy(sctx->mc_ip, mc_ip);
  sctx->ib_port = ib_port;
  sctx->mc_port = mc_port;
  sctx->cache_size = (long)cache_g_size << 30;
  p_info("memcache addr: %s:%d\n", mc_ip, mc_port);
  p_info("IB port :%d\n", ib_port);
  sctx->mi = mt_create_ctx(mc_ip, mc_port);
  client_conn_init(sctx);
  //thread for recv
  {
    pthread_t poll_thread;
    res = pthread_create(&poll_thread, NULL, poll_routine, sctx);
    assert(!res);
  }
  return sctx;
}
void thread_info_init(struct client_thread_ctx *ctx){
  BUILD_BUG_ON(sizeof(union req_union) != 32);
  ctx->req = (union req_union *)memalign(64, sizeof(union req_union));
  assert(ctx->req);
  ctx->req_mr = ibv_reg_mr(ctx->sctx->pd, ctx->req, 32, MR_PERMISSION);
  ctx->page_buf = memalign(1<<12, 1<<12);
  ctx->page_mr = ibv_reg_mr(ctx->sctx->pd, ctx->page_buf, 1<<12, MR_PERMISSION);
}
struct client_thread_ctx *api_new_thread_init(struct client_thread_ctx *old_ctx){
  struct client_thread_ctx *ctx;
  ctx = (struct client_thread_ctx *)malloc(sizeof(struct client_thread_ctx));
  ctx->sctx = old_ctx->sctx;
  ++ctx->sctx->ref;
  thread_info_init(ctx);
  return ctx;
}
struct client_thread_ctx *api_main_thread_init(int argc, char *argv[]){
  struct client_thread_ctx *ctx;
  ctx = (struct client_thread_ctx *)malloc(sizeof(struct client_thread_ctx));
  assert(ctx);
  ctx->sctx = create_shared_ctx(argc, argv);
  ++ctx->sctx->ref;
  thread_info_init(ctx);
  return ctx;
}
unsigned short str_hash(const char *s){
  unsigned short res = 5381;
  while(*s){
    res = (res << 5) + res + *(s ++);
  }
  return res;
}
Addr api_alloc_dsm(struct client_thread_ctx *ctx, const char *name, long size){
  union req_union req;
  union res_union res;
  unsigned status;
  unsigned short h;
  Addr addr;

  h = str_hash(name);
  p_info("file id: %d\n", h);
  req.req.type = REQ_alloc_dsm;
  req.req.alloc_dsm.size = size;
  req.req.alloc_dsm.mp_id = h;
  pingpong(ctx, 0, &req, &res);
  status = res.res.status;
  if(status == RES_nomem){
    p_info("no memroy to create new file\n");
  }
  if(status == RES_exist){
    p_info("file already existed\n");
  }else if(status == RES_success){
    p_info("file_create success\n");
  }else{
    p_die("unknwon error\n");
  }
  addr.val = 0;
  if(status == RES_success){
    addr.region_id = h;
  }
  return addr;
}

unsigned long api_write_trans(struct client_thread_ctx *ctx, Addr start, Addr end){
  union req_union req;
  union res_union res;
  unsigned status;
  req.req.type = REQ_write_trans_begin;
  req.req.trans_write.start = start.val;
  req.req.trans_write.end = end.val;
  pingpong(ctx, 0, &req, &res);
  status = res.res.status;
  assert(status == RES_success);
  return res.res.commit_id;
}
void api_write_commit(struct client_thread_ctx *ctx, unsigned long id){
  union req_union req;
  union res_union res;
  unsigned status;
  req.req.type = REQ_write_trans_end;
  req.req.commit.commit_id = id;
  pingpong(ctx, 0, &req, &res);
  status = res.res.status;
  assert(status == RES_success);
}
unsigned long api_read_trans(struct client_thread_ctx *ctx, unsigned short mp_id){
  union req_union req;
  union res_union res;
  unsigned status;
  req.req.type = REQ_read_trans_begin;
  req.req.trans_read.mp_id = mp_id;
  pingpong(ctx, 0, &req, &res);
  status = res.res.status;
  assert(status == RES_success);
  return res.res.commit_id;
}
void api_read_commit(struct client_thread_ctx *ctx, unsigned long commit_id){
  union req_union req;
  union res_union res;
  unsigned status;
  req.req.type = REQ_read_trans_end;
  req.req.commit.commit_id = commit_id;
  pingpong(ctx, 0, &req, &res);
  status = res.res.status;
  assert(status == RES_success);
}
void get_addr(struct client_thread_ctx *ctx, unsigned short mp_id, int n, unsigned pfn[], unsigned long dest[], bool read){
  union req_union req;
  union res_union res;
  unsigned status;
  assert(n <= MAX_AGGR_PFN);
  if(read)req.req.type = REQ_page_get_read_addr;
  else req.req.type = REQ_page_get_write_addr;
  req.req.get_addr.mp_id = mp_id;
  memcpy(req.req.get_addr.pfn, pfn, sizeof(unsigned) * n);
  pingpong(ctx, 0, &req, &res);
  status = res.res.status;
  assert(status == RES_success);
  memcpy(dest, res.res.data, sizeof(unsigned long) * MAX_AGGR_PFN);
}
void api_write(struct client_thread_ctx *ctx, void *buf, long size, Addr addr){
  const int small_align = 64;
  unsigned long start, end, start_pfn, end_pfn;
  start = addr.offset; end = addr.offset + size;
  start_pfn = start >> 12; end_pfn = end >> 12;
  unsigned long i, agg, j;
  unsigned long real_addr[MAX_AGGR_PFN];
  unsigned pfn_arr[MAX_AGGR_PFN];
  for(i = start_pfn; i <= end_pfn; i += MAX_AGGR_PFN){
    agg = min(MAX_AGGR_PFN, (int)(end_pfn - start_pfn + 1));
    for(j = 0; j < agg; j++)pfn_arr[j] = i + j;
    get_addr(ctx, addr.region_id, agg, pfn_arr, real_addr, false);
    for(j = 0; j < agg; j++)p_info("pfn %u, addr %lx\n", (unsigned)(i + j), real_addr[j]);
  }
  return ;
}
void api_read(struct client_thread_ctx *ctx, void *buf, long size, Addr addr){
  const int small_align = 64;
  unsigned long start, end, start_pfn, end_pfn;
  start = addr.offset; end = addr.offset + size;
  start_pfn = start >> 12; end_pfn = end >> 12;
  unsigned long i, agg, j;
  unsigned long real_addr[MAX_AGGR_PFN];
  unsigned pfn_arr[MAX_AGGR_PFN];
  for(i = start_pfn; i <= end_pfn; i += MAX_AGGR_PFN){
    agg = min(MAX_AGGR_PFN, (int)(end_pfn - start_pfn + 1));
    for(j = 0; j < agg; j++)pfn_arr[j] = i + j;
    get_addr(ctx, addr.region_id, agg, pfn_arr, real_addr, true);
    for(j = 0; j < agg; j++)p_info("pfn %u, addr %lx\n", (unsigned)(i + j), real_addr[j]);
  }
  return ;
}